package com.iotechn.uninotify.controller;

import com.alibaba.fastjson.JSONObject;
import com.iotechn.uninotify.compent.CacheComponent;
import com.iotechn.uninotify.exception.NotifyExceptionDefinition;
import com.iotechn.uninotify.exception.NotifyServiceException;
import com.iotechn.uninotify.service.biz.DeveloperBizService;
import com.iotechn.uninotify.service.biz.WxMpBizService;
import com.iotechn.uninotify.service.open.DeveloperServiceImpl;
import com.iotechn.uninotify.util.SHAUtil;
import com.iotechn.uninotify.util.WxMpSignUtil;
import com.iotechn.uninotify.util.XMLParseUtil;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: rize
 * Date: 2019/12/26
 * Time: 18:52
 */
@Controller
@RequestMapping("/cb")
public class CallbackController {

    private static final Logger logger = LoggerFactory.getLogger(CallbackController.class);

    @Value("${com.iotechn.uninotify.mp.ase-key}")
    private String encodingASEKey;

    @Value("${com.iotechn.uninotify.mp.token}")
    private String token;

    @Value("${com.iotechn.uninotify.name}")
    private String name;

    @Autowired
    private CacheComponent cacheComponent;

    @Autowired
    private DeveloperBizService developerBizService;

    /**
     * 用于回调通知的线程
     */
    private ExecutorService executorService = Executors.newFixedThreadPool(2);

    @Autowired
    private WxMpBizService wxMpBizService;

    @Value("${com.iotechn.bind.success.template-id}")
    private String bindNotifyTemplateId;

    private OkHttpClient okHttpClient = new OkHttpClient();

    @RequestMapping(value = "/wxmp", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String wxmpcall(HttpServletRequest request, @RequestBody(required = false) String requestBody) throws Exception {
        logger.info("[wxmpcall] 回调：" + JSONObject.toJSONString(request.getParameterMap()));
        logger.info("[wxmpcall] 回调：" + requestBody);
        wechatCheck(request.getParameter("timestamp"), request.getParameter("nonce"), request.getParameter("signature"));
        if (!StringUtils.isEmpty(requestBody)) {
            Map<String, String> bodyMap = XMLParseUtil.extractLineObj(requestBody);
            String encrypt = bodyMap.get("Encrypt");
            String decrypt = WxMpSignUtil.decrypt(this.encodingASEKey, encrypt);
            // 真正的报文（解密后的报文）
            Map<String, String> realParam = XMLParseUtil.extractLineObj(decrypt);
            if (realParam.get("Event").equalsIgnoreCase("subscribe") || realParam.get("Event").equalsIgnoreCase("SCAN")) {
                //获取场景值
                String eventKey = realParam.get("EventKey");
                if (eventKey.startsWith("qrscene_")) {
                    eventKey = eventKey.replace("qrscene_", "");
                }
                //将其注册到developer用户表
                JSONObject obj = cacheComponent.getObj(DeveloperServiceImpl.CA_TMP_TOKEN + eventKey, JSONObject.class);
                if (obj != null) {
                    String appId = obj.getString("appId");
                    String userId = obj.getString("userId");
                    String appSecret = obj.getString("appSecret");
                    String openid = request.getParameter("openid");
                    int register = developerBizService.register(appId, userId, openid);
                    //进行成功回调
                    String notifyUrl = obj.getString("notifyUrl");
                    if (!StringUtils.isEmpty(notifyUrl)) {
                        //若回调接口存在。则通知对方服务器
                        executorService.execute(() -> {
                            try {
                                TreeSet<String> set = new TreeSet<>();
                                set.add(register + "");
                                long timestamp = System.currentTimeMillis();
                                set.add(timestamp + "");
                                set.add(appSecret);
                                // 排序好的明文
                                String raw = set.stream().collect(Collectors.joining());
                                // 签名
                                String sign = SHAUtil.shaEncode(URLEncoder.encode(raw, "utf-8"));
                                FormBody formBody = new FormBody.Builder().add("status", register + "").add("timestamp", timestamp + "").add("sign", sign).build();
                                // 通知对方
                                okHttpClient.newCall(new Request.Builder().url(notifyUrl).post(formBody).build()).execute().body().string();
                            } catch (Exception e) {
                                // 回调通知异常
                                logger.warn("[回调通知] 异常");
                            }
                        });
                    }
                    // 通知用户成功
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                String accessToken = wxMpBizService.getAccessToken();
                                MediaType mediaType = MediaType.parse("application/json");
                                JSONObject data = new JSONObject();
                                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                data.put("first", this.buildParamObj("恭喜您绑定通知中心", "#0"));
                                data.put("remark", this.buildParamObj("感谢您使用" + CallbackController.this.name + "通知中心", "#0"));
                                data.put("keyword1", this.buildParamObj("绑定成功", "#0"));
                                data.put("keyword2", this.buildParamObj(simpleDateFormat.format(new Date()), "#0"));
                                JSONObject paramObj = new JSONObject();
                                paramObj.put("touser", openid);
                                paramObj.put("template_id", bindNotifyTemplateId);
                                paramObj.put("data", data);
                                okhttp3.RequestBody body = okhttp3.RequestBody.create(mediaType, paramObj.toJSONString());
                                okHttpClient.newCall(
                                        new Request.Builder()
                                                .post(body)
                                                .url("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken).build())
                                        .execute().body().string();
                            } catch (Exception e) {
                                logger.error("[通知用户] 异常", e);
                            }
                        }

                        private JSONObject buildParamObj(String value, String color) {
                            JSONObject item = new JSONObject();
                            item.put("value", value);
                            item.put("color", color);
                            return item;
                        }

                    });
                }
                // 缓存已经过期, 不做处理
            }
        }
        return request.getParameter("echostr");
    }


    /**
     * 校验是否是伪造请求
     *
     * @param timestamp
     * @param nonce
     * @param signature
     * @return
     */
    private String wechatCheck(String timestamp, String nonce, String signature) {
        /*
         * 规则描述
         *1. 将token、timestamp、nonce三个参数进行字典序排序
         *2. 将三个参数字符串拼接成一个字符串进行sha1加密
         *3. 开发者获得加密后的字符串可与signature对比，标识该请求来源于微信
         */

        String token = this.token;

        LinkedList<String> list = new LinkedList<>();
        list.add(nonce);
        list.add(timestamp);
        list.add(token);
        Collections.sort(list);
        String target = DigestUtils.sha1Hex(list.stream().collect(Collectors.joining()));
        if (!target.equals(signature)) {
            throw new RuntimeException("并不是微信来的请求");
        }
        return signature;
    }

}
